﻿using System;
using System.Collections.Generic;
using System.Text;
using WinSW;
using WinSW.Plugins;
using winswTests.Extensions;

namespace winswTests.Util
{
    /// <summary>
    /// Configuration XML builder, which simplifies testing of WinSW Configuration file.
    /// </summary>
    class ConfigXmlBuilder
    {
        public string Name { get; set; }
        public string Id { get; set; }
        public string Description { get; set; }
        public string Executable { get; set; }
        public bool PrintXMLVersion { get; set; }
        public string XMLComment { get; set; }
        public List<string> ExtensionXmls { get; private set; }

        private readonly List<string> configEntries;

        // TODO: Switch to the initializer?
        private ConfigXmlBuilder()
        {
            this.configEntries = new List<string>();
            this.ExtensionXmls = new List<string>();
        }

        public static ConfigXmlBuilder create(string id = null, string name = null,
            string description = null, string executable = null, bool printXMLVersion = true,
            string xmlComment = "")
        {
            var config = new ConfigXmlBuilder();
            config.Id = id ?? "myapp";
            config.Name = name ?? "MyApp Service";
            config.Description = description ?? "MyApp Service (powered by WinSW)";
            config.Executable = executable ?? "%BASE%\\myExecutable.exe";
            config.PrintXMLVersion = printXMLVersion;
            config.XMLComment = (xmlComment != null && xmlComment.Length == 0)
                ? "Just a sample configuration file generated by the test suite"
                : xmlComment;
            return config;
        }

        public string ToXMLString(bool dumpConfig = false)
        {
            var str = new StringBuilder();
            if (this.PrintXMLVersion)
            {
                // TODO: The encoding is generally wrong
                str.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
            }

            if (this.XMLComment != null)
            {
                str.AppendFormat("<!--{0}-->\n", this.XMLComment);
            }

            str.Append("<service>\n");
            str.AppendFormat("  <id>{0}</id>\n", this.Id);
            str.AppendFormat("  <name>{0}</name>\n", this.Name);
            str.AppendFormat("  <description>{0}</description>\n", this.Description);
            str.AppendFormat("  <executable>{0}</executable>\n", this.Executable);
            foreach (string entry in this.configEntries)
            {
                // We do not care much about pretty formatting here
                str.AppendFormat("  {0}\n", entry);
            }

            // Extensions
            if (this.ExtensionXmls.Count > 0)
            {
                str.Append("  <extensions>\n");
                foreach (string xml in this.ExtensionXmls)
                {
                    str.Append(xml);
                }

                str.Append("  </extensions>\n");
            }

            str.Append("</service>\n");
            string res = str.ToString();
            if (dumpConfig)
            {
                Console.Out.WriteLine("Produced config:");
                Console.Out.WriteLine(res);
            }

            return res;
        }

        public XmlServiceConfig ToServiceDescriptor(bool dumpConfig = false)
        {
            return XmlServiceConfig.FromXML(this.ToXMLString(dumpConfig));
        }

        public ConfigXmlBuilder WithRawEntry(string entry)
        {
            this.configEntries.Add(entry);
            return this;
        }

        public ConfigXmlBuilder WithTag(string tagName, string value)
        {
            return this.WithRawEntry(string.Format("<{0}>{1}</{0}>", tagName, value));
        }

        public ConfigXmlBuilder WithRunawayProcessKiller(RunawayProcessKillerExtension ext, string extensionId = "killRunawayProcess", bool enabled = true)
        {
            string fullyQualifiedExtensionName = ExtensionTestBase.GetExtensionClassNameWithAssembly(typeof(RunawayProcessKillerExtension));
            var str = new StringBuilder();
            str.AppendFormat("    <extension enabled=\"{0}\" className=\"{1}\" id=\"{2}\">\n", new object[] { enabled, fullyQualifiedExtensionName, extensionId });
            str.AppendFormat("      <pidfile>{0}</pidfile>\n", ext.PidFile);
            str.AppendFormat("      <stopTimeout>{0}</stopTimeout>\n", ext.StopTimeout.TotalMilliseconds);
            str.AppendFormat("      <stopParentFirst>{0}</stopParentFirst>\n", ext.StopParentProcessFirst);
            str.AppendFormat("      <checkWinSWEnvironmentVariable>{0}</checkWinSWEnvironmentVariable>\n", ext.CheckWinSWEnvironmentVariable);
            str.Append("    </extension>\n");
            this.ExtensionXmls.Add(str.ToString());

            return this;
        }

        public ConfigXmlBuilder WithDownload(Download download)
        {
            var xml = new StringBuilder();
            xml.Append($"<download from=\"{download.From}\" to=\"{download.To}\" failOnError=\"{download.FailOnError}\"");

            // Authentication
            if (download.Auth != Download.AuthType.None)
            {
                xml.Append($" auth=\"{download.Auth}\"");
                if (download.Auth == Download.AuthType.Basic)
                {
                    string username = download.Username;
                    if (username != null)
                    {
                        xml.Append($" user=\"{username}\"");
                    }

                    string password = download.Password;
                    if (password != null)
                    {
                        xml.Append($" password=\"{password}\"");
                    }
                }

                if (download.UnsecureAuth)
                {
                    xml.Append(" unsecureAuth=\"true\"");
                }
            }

            xml.Append("/>");

            return this.WithRawEntry(xml.ToString());
        }

        public ConfigXmlBuilder WithDelayedAutoStart()
        {
            return this.WithRawEntry("<delayedAutoStart/>");
        }
    }
}
